package com.ukefu.webim.service.httpserver;

import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.util.Map;
import java.util.regex.Pattern;

import javax.activation.MimetypesFileTypeMap;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.LoggerFactory;

import com.ukefu.util.UKTools;
import com.ukefu.webim.web.model.SystemConfig;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelProgressiveFuture;
import io.netty.channel.ChannelProgressiveFutureListener;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.stream.ChunkedFile;
import io.netty.util.CharsetUtil;

public class HttpFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest>{

	private org.slf4j.Logger logger = LoggerFactory.getLogger(HttpFileServerHandler.class) ;
	private final String url;
	private final String redirect ;
	public HttpFileServerHandler(String url  , String redirect) {
		this.url = url;
		this.redirect = redirect ;
	}

	private static void sendRedirect(ChannelHandlerContext ctx, String newUri){
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.FOUND);
        response.headers().set(HttpHeaderNames.LOCATION, newUri);
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		if (ctx.channel().isActive()){
			sendError(ctx,INTERNAL_SERVER_ERROR);
		}
	}



	private static final Pattern INSECURE_URI=Pattern.compile(".*[<>&\"].*");

	public String sanitizeUri(String uri){
		try{
			uri= URLDecoder.decode(uri,"UTF-8");
		}catch (Exception e){
			try{
				uri= URLDecoder.decode(uri,"ISO-8859-1");
			}catch (Exception ew){
				ew.printStackTrace();
			}
		}

		if (!uri.startsWith("/")){
			return null;

		}

		uri=uri.replace('/',File.separatorChar);
		if (uri.contains(File.separator+'.')||uri.startsWith(".")||uri.endsWith(".")||INSECURE_URI.matcher(uri).matches()){
			return null;
		}

		return uri;//System.getProperty("user.dir")+uri

	}


	private void sendError(ChannelHandlerContext channelHandlerContext, HttpResponseStatus status) {
		FullHttpResponse response=new DefaultFullHttpResponse(
				HTTP_1_1,status,Unpooled.copiedBuffer("Failure: "+status.toString()+"\r\n",
						CharsetUtil.UTF_8));
		response.headers().set(CONTENT_TYPE,"text/plain; charset=UTF-8");
		channelHandlerContext.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);

	}
	
	private void sendError(ChannelHandlerContext channelHandlerContext, HttpResponseStatus status , String ip) {
		FullHttpResponse response=new DefaultFullHttpResponse(
				HTTP_1_1,status,Unpooled.copiedBuffer("Failure (IP:"+ip+"): "+status.toString()+"\r\n",
						CharsetUtil.UTF_8));
		logger.info("Failure (IP:"+ip+"): "+status.toString());
		response.headers().set(CONTENT_TYPE,"text/plain; charset=UTF-8");
		channelHandlerContext.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);

	}


	private void setContentTypeHeader(HttpResponse httpResponse, File file) throws UnsupportedEncodingException {

		MimetypesFileTypeMap mimetypesFileTypeMap=new MimetypesFileTypeMap();
		httpResponse.headers().set(CONTENT_TYPE,mimetypesFileTypeMap.getContentType(file.getPath()));
		
		httpResponse.headers().set("Content-Type" , "application/octet-stream");
		httpResponse.headers().set("Content-Lenght" , String.valueOf(file.length()));
    	httpResponse.headers().set("Content-Disposition", "attachment;filename="+java.net.URLEncoder.encode(file.getName(), "UTF-8"));  
	}

	@Override
	protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpRequest fullHttpRequest) throws Exception {
		// TODO Auto-generated method stub
		InetSocketAddress insocket = (InetSocketAddress) channelHandlerContext.channel()
                .remoteAddress();
		SystemConfig systemConfig = UKTools.getSystemConfig();
		if (!fullHttpRequest.decoderResult().isSuccess()){
			sendError(channelHandlerContext, HttpResponseStatus.BAD_REQUEST);
			return;
		}
		if (systemConfig != null && !StringUtils.isBlank(systemConfig.getWhitelistip()) && systemConfig.getWhitelistip().indexOf(insocket.getAddress().getHostAddress()) < 0){
			sendError(channelHandlerContext, HttpResponseStatus.NON_AUTHORITATIVE_INFORMATION , insocket.getAddress().getHostAddress());
			return;
		}

		if (fullHttpRequest.method()!= HttpMethod.GET){
			sendError(channelHandlerContext,HttpResponseStatus.METHOD_NOT_ALLOWED);
			return;
		}

		String uri = fullHttpRequest.uri();
		
		final String path = sanitizeUri(uri);
		if (path==null){
			sendError(channelHandlerContext,HttpResponseStatus.FORBIDDEN);
			return;
		}
		File file = null ;
		Map<String, String> parmMap = new RequestParser(fullHttpRequest).parse();
		if(parmMap.get("id")!=null) {
			file = new File(url , parmMap.get("id"));
		}
		if (file == null || file.isHidden() || !file.exists()){
			String host = fullHttpRequest.headers().getAsString("Host") ;
			if(!StringUtils.isBlank(host) && host.indexOf(":") > 0) {
				uri = host.substring(0 , host.indexOf(":")) ;
			}else {
				uri = host ;
			}
			if(!StringUtils.isBlank(redirect) && !"0".equals(redirect)) {
				uri = "https://"+uri+":"+redirect ;
			}else {
				uri = "https://"+uri ;
			}
			sendRedirect(channelHandlerContext,uri);
			return;
		}

		if (!file.isFile()){
			sendError(channelHandlerContext,HttpResponseStatus.FORBIDDEN);
			return;
		}

		RandomAccessFile randomAccessFile=null;
		try{
			randomAccessFile=new RandomAccessFile(file,"r");
		}catch (FileNotFoundException e){
			e.printStackTrace();
			sendError(channelHandlerContext,HttpResponseStatus.NOT_FOUND);
			return;
		}

		Long fileLength=randomAccessFile.length();
		HttpResponse httpResponse=new DefaultHttpResponse(HTTP_1_1,OK);
		setContentTypeHeader(httpResponse,file);

		channelHandlerContext.writeAndFlush(httpResponse);
		ChannelFuture sendFileFuture = channelHandlerContext.write(
				new ChunkedFile(randomAccessFile,0,fileLength,8192),channelHandlerContext.newProgressivePromise());

		sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
			public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) {
			}

			public void operationComplete(ChannelProgressiveFuture future) {
			}
		});


		ChannelFuture lastChannelFuture=channelHandlerContext.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
		lastChannelFuture.addListener(ChannelFutureListener.CLOSE );
	}

}